1. Header组件单元测试

header组件主要由一个title,和一个输入框组成,输入框有以下几个处理情况:

  • 输入框初始化为空
  • 输入框内容为空时,回车事件不做任何操作
  • 输入框内容不为空时,回车事件会调用添加todo的函数,并且清空输入框内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// Header.jsx
export default class Header extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ""
}
this.handleKeyUp = this.handleKeyUp.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleKeyUp(e) {
const { value } = this.state;
if (e.keyCode === 13 && value) {
this.props.addUndoItem(value)
this.setState({value: ""})
}
}
handleInputChange(e) {
this.setState({
value: e.target.value
})
}
render() {
const { value } = this.state;
return (
<div className="header">
<div className="header-content">TodoList
<input
value={value}
data-test="input"
className="header-input"
placeholder="add todo"
onKeyUp={this.handleKeyUp}
onChange={this.handleInputChange}
/>
</div>
</div>
)
}
}
// test/Header.test.jsx
import React from "react";
import { shallow } from "enzyme";
import Header from "../Header";
import { findTestWrapper } from "common/util/testUtils"

describe("Header Componet Test", () => {
it("样式渲染正常", () => {
const wrapper = shallow(<Header />);
expect(wrapper).toMatchSnapshot();
})

it("组件包含输入框", () => {
const wrapper = shallow(<Header />);
const inputElem = findTestWrapper(wrapper, "input");
expect(inputElem.length).toBe(1);
})

it("输入框内容初始化应该为空", () => {
const wrapper = shallow(<Header />);
const inputElem = wrapper.find("[data-test='input']");
expect(inputElem.prop("value")).toEqual("");
})


it("输入框随用户输入时发生改变", () => {
const wrapper = shallow(<Header />);
const inputElem = wrapper.find("[data-test='input']");
/**
* 模拟change事件,输入test nyan
*/
inputElem.simulate("change", {
target: {
value: "test nyan"
}
})
expect(wrapper.state("value")).toEqual("test nyan");
})

it("输入框没有内容时,回车时无反应", () => {
const fn = jest.fn();
const wrapper = shallow(<Header addUndoItem={fn} />);
const inputElem = wrapper.find("[data-test='input']");
wrapper.setState({ value: "" })
inputElem.simulate("kepUp", {
keyCode: 13
})
expect(fn).not.toHaveBeenCalled();
})

it("输入框有内容触发回车时,函数应该被调用", () => {
const fn = jest.fn();
const wrapper = shallow(<Header addUndoItem={fn} />);
const inputElem = wrapper.find("[data-test='input']");
// 准备数据
wrapper.setState({ value: "jest react" })
/**
* 设置keyUp的keyCode模拟回车
*/
inputElem.simulate("keyUp", {
keyCode: 13
})
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenLastCalledWith("jest react"); // 最后的参数
})

it("输入框有内容触发回车时,内容应该被清除", () => {
const fn = jest.fn();
const wrapper = shallow(<Header addUndoItem={fn} />);
const inputElem = wrapper.find("[data-test='input']");
// 准备数据
wrapper.setState({ value: "jest react add one" })
/**
* 设置keyUp的keyCode模拟回车
*/
inputElem.simulate("keyUp", {
keyCode: 13
})
const newInputElem = wrapper.find("[data-test='input']");
expect(newInputElem.prop("value")).toBe("");
})
})

2. TodoList组件单元测试

TodoList组件主要控制undoList数据状态的改变,实现改变数据项显示状态(输入框或文字)。

  • 首先数据项undoList初始化为空
  • 其次判断TodoList组件所包含那些子组件以及属性方法
  • 每一个数据项都有修改和删除操作
  • 通过状态的改变进行数据项修改
  • 点击每一个数据项,会调用状态改变函数
  • 当输入框失去焦点时,改变数据项的状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import React from "react";
import Header from "../components/Header"
import UndoList from "../components/UndoList";

export default class TodoList extends React.Component {
constructor(props) {
super(props);
this.state = {
undoList: []
}
this.deleteItem = this.deleteItem.bind(this);
this.addUndoItem = this.addUndoItem.bind(this);
this.changeStatus = this.changeStatus.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.handleValueChange = this.handleValueChange.bind(this);
}
addUndoItem(value) {
this.setState({
undoList: [...this.state.undoList, {
status: "div",
value
}]
})
}
deleteItem(index) {
const newUndoList = [...this.state.undoList]
newUndoList.splice(index, 1)
this.setState({
undoList: newUndoList
})
}
changeStatus(index) {
const newUndoList = this.state.undoList.map((item, listIndex) => {
if (listIndex === index) {
return {
...item,
status: "input"
}
}
return {
...item,
status: "div"
}
})
this.setState({
undoList: newUndoList
})
}

handleBlur(index) {
const newUndoList = this.state.undoList.map((item, listIndex) => {
if (listIndex === index) {
return {
...item,
status: "div"
}
}
return item
})
this.setState({
undoList: newUndoList
})
}
handleValueChange(index, value) {
const newUndoList = this.state.undoList.map((item, listIndex) => {
if (listIndex === index) {
return {
...item,
value
}
}
return item
})
this.setState({
undoList: newUndoList
})
}
render() {
const { undoList } = this.state;
return (
<>
<Header addUndoItem={this.addUndoItem} />
<UndoList
list={undoList}
deleteItem={this.deleteItem}
changeStatus={this.changeStatus}
handleBlur={this.handleBlur}
handleValueChange={this.handleValueChange}
/>
</>
)
}
}
// todoList.test.js
import React from "react";
import { shallow } from "enzyme";
import TodoList from "../../../containers/TodoList";

describe("TodoList Comonent Test", () => {

it("初始化列表为空", () => {
const wrapper = shallow(<TodoList />);
expect(wrapper.state("undoList")).toEqual([]);
})

it("Header组件存在addUndoItem的属性", () => {
const wrapper = shallow(<TodoList />);
const header = wrapper.find("Header");
//expect(header.prop("addUndoItem")).toBe(wrapper.instance().addUndoItem);
expect(header.prop("addUndoItem")).toBeTruthy();
})

it("addUndoItem方法被调用时,undoList数据项新增", () => {
const wrapper = shallow(<TodoList />);
/**
* const header = wrapper.find("Header");
* const addFn = header.prop("addUndoItem");
* addFn("add item one");
* 这样操作,测试就会跟Header组件耦合(像集成测试),应该修改为一下方案
*/
const content = "add item one"
wrapper.instance().addUndoItem(content)
expect(wrapper.state("undoList").length).toBe(1);
expect(wrapper.state("undoList")[0]).toEqual({
status: "div",
value: content
})
})

it("UndoList 组件应该有接收list,deleteItem,changeStatus, handleBlur属性", () => {
const wrapper = shallow(<TodoList />);
const undoList = wrapper.find("UndoList");
expect(undoList.prop("list")).toBeTruthy();
expect(undoList.prop("deleteItem")).toBeTruthy();
expect(undoList.prop("changeStatus")).toBeTruthy();
expect(undoList.prop("handleBlur")).toBeTruthy();
})

it("deleteItem被执行时,应该删除对应数据项", () => {
const wrapper = shallow(<TodoList />);
const data =[
{status: "div",value: "react"},
{status: "div",value: "ject"},
{status: "div",value: "enzyme"},
]
wrapper.setState({undoList: data});
wrapper.instance().deleteItem(1);
expect(wrapper.state("undoList")).toEqual([data[0], data[2]])
})

it("changeStatus被执行时,undoList数据项被修改", () => {
const wrapper = shallow(<TodoList />);
const data =[
{status: "div",value: "react"},
{status: "div",value: "ject"},
{status: "div",value: "enzyme"},
]
wrapper.setState({undoList: data});
wrapper.instance().changeStatus(1);
expect(wrapper.state("undoList")[1]).toEqual({
...data[1],
status: "input"
})
})

it("handleBlur被执行时,undoList数据项被修改", () => {
const wrapper = shallow(<TodoList />);
const data =[
{status: "input",value: "react"},
{status: "div",value: "ject"},
{status: "div",value: "enzyme"},
]
wrapper.setState({undoList: data});
wrapper.instance().handleBlur(0);
expect(wrapper.state("undoList")[0]).toEqual({
...data[0],
status: "div"
})
})
})

3. undoList组件单元测试

undoList组件是TodoList组件的子组件,控制显示todoList数据,主要包含title、统计添加的todo数据项个数、显示添加的数据项,以及父亲组件传入修改的数据项。

  • 当没有添加todo数据项时,count的长度为0
  • 当添加了todo数据项时,显示数量以及对应的数据项
  • 当有todo数据项时,每个数据项显示删除按钮
  • 当有todo数据项时,点击数据项时触发改变数据项状态函数
  • 当数据项状态为可修改时,失去焦点时触发失去失去焦点函数
  • 当数据项状态为可修改时,修改数据时触发数据项值改变的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import React from "react";

export default class UndoList extends React.Component {

render() {
const { list, deleteItem, changeStatus, handleBlur, handleValueChange } = this.props;
return (
<div className="undo-list">
<div className="undo-list-title">
正在进行
<span className="undo-list-count" data-test="count">{list.length}</span>
</div>

<ul className="undo-list-content">
{
list.map((item, index) => {
return (
<li
className="undo-list-item"
data-test="listItem"
key={`${item}-${index}`}
onClick={() => changeStatus(index)}
>
{item.status === "div" ? item.value : (
<input
value={item.value}
data-test="input"
className="undo-list-input"
onBlur={() => handleBlur(index)}
onChange={(e) => handleValueChange(index, e.target.value)}
/>
)}
<span
className="undo-list-delete"
data-test="deleteItem"
onClick={(e) => {
e.stopPropagation()
deleteItem(index)
}}>-</span>
</li>
)
})
}
</ul>
</div>
)
}
}

// undoList.test.jsx
import React from "react";
import { shallow } from "enzyme";
import UndoList from "../../UndoList";
import { findTestWrapper } from "../../../common/util/testUtils";

describe("UndoList Component Test", () => {
it("当数据为空数组时,count为0,列表无内容", () => {
const undoList = [];
const wrapper = shallow(<UndoList list={undoList} />);
const countElem = findTestWrapper(wrapper, "count");
const listItems = findTestWrapper(wrapper, "listItem");
expect(countElem.text()).toBe("0");
expect(listItems.length).toEqual(0);
})

it("当数据不为空数组时,显示count,列表内容不为空", () => {
const undoList = [
{ status: "div", value: "react" },
{ status: "div", value: "ject" },
{ status: "div", value: "enzyme" },
];
const wrapper = shallow(<UndoList list={undoList} />);
const countElem = findTestWrapper(wrapper, "count");
const listItems = findTestWrapper(wrapper, "listItem");
expect(countElem.text()).toBe("3");
expect(listItems.length).toEqual(3);
})

it("当数据不为空数组时,应该有删除按钮", () => {
const undoList = [
{ status: "div", value: "react" },
{ status: "div", value: "ject" },
{ status: "div", value: "enzyme" },
];
const wrapper = shallow(<UndoList list={undoList} />);
const deleteItem = findTestWrapper(wrapper, "deleteItem");
expect(deleteItem.length).toEqual(3);
})

it("当数据不为空数组时,点击某个删除按钮,调用删除方法", () => {
const fn = jest.fn();
const index = 1;
const undoList = [
{ status: "div", value: "react" },
{ status: "div", value: "ject" },
{ status: "div", value: "enzyme" },
];
const wrapper = shallow(<UndoList list={undoList} deleteItem={fn} />);
const deleteItem = findTestWrapper(wrapper, "deleteItem");
deleteItem.at(index).simulate("click", {
stopPropagation: () => { } // 阻止事件冒泡
});
expect(fn).toHaveBeenCalledWith(index);
})

it("当某一项被点击时,触发执行changeStatus函数", () => {
const fn = jest.fn();
const index = 1;
const undoList = [
{ status: "div", value: "react" },
{ status: "div", value: "ject" },
{ status: "div", value: "enzyme" },
];
const wrapper = shallow(<UndoList list={undoList} changeStatus={fn} />);
const changeStatus = findTestWrapper(wrapper, "listItem");
changeStatus.at(index).simulate("click");
expect(fn).toHaveBeenCalledWith(index);
})

it("当某一项的状态为‘input’时,存在一个输入框", () => {
const undoList = [
{ status: "input", value: "react" },
{ status: "div", value: "ject" },
{ status: "div", value: "enzyme" },
];
const wrapper = shallow(<UndoList list={undoList} />);
const inputElem = findTestWrapper(wrapper, "input");
expect(inputElem.length).toBe(1);
})

it("当某一项失去焦点时,执行handleBlur函数", () => {
const fn = jest.fn();
const undoList = [
{ status: "input", value: "react" },
{ status: "div", value: "ject" },
{ status: "div", value: "enzyme" },
];
const wrapper = shallow(<UndoList list={undoList} handleBlur={fn} />);
const inputElem = findTestWrapper(wrapper, "input");
inputElem.simulate("blur");
expect(fn).toHaveBeenCalledWith(0);
})

it('当某一个输入框变更时,触发 handleValueChange 方法', () => {
const listData = [
{ status: 'input',value: 'jest' },
]
const value = 'react';
const fn = jest.fn();
const wrapper = shallow(<UndoList handleValueChange={fn} list={listData}/>);
const inputElem = findTestWrapper(wrapper, "input");
inputElem.simulate('change', {
target: {value}
});
expect(fn).toHaveBeenLastCalledWith(0, value);
});
})